﻿using System;
using System.Collections.Generic;
using System.ServiceModel;
using System.ServiceModel.Dispatcher;
using System.Threading;
using System.Timers;
using Sripirom.ATS.Core.ServiceModel.Dispatcher;
using Timer = System.Timers.Timer;

namespace Sripirom.ATS.Core.ServiceModel.Dispatcher
{
    /// <summary>
    /// This class contains the implementation for the
    /// object pool. This implements the IInstanceProvider
    /// in order to be able to plugin to the service model
    /// layer.
    /// </summary>
    class ObjectPoolInstanceProvider : IInstanceProvider
    {
        #region Constants
        const long IDLE_TIMEOUT = 1000;  // 300000 * 5;
        const int DEFAULT_MAX_POOL_SIZE = 1048576;
        const int DEFAULT_CREATION_TIMEOUT = 60000;
        const int DEFAULT_MIN_POOL_SIZE = 0;
        #endregion

        #region Private Fields
        int _maxPoolSize;
        int _minPoolSize;
        int _creationTimeout;

        // Type of the objects created in the pool.
        Type _instanceType;

        // Stack used for storing the objects in the pool.
        Stack<object> _pool;

        // Semaphore uesd to synchronize access to
        // the pool.
        Semaphore  _availableCount;

        // A lock should be acquired before accessing the
        // pool stack.
        object _poolLock = new object();

        // Keeps track of the number of objects returned
        // from the pool.
        int _activeObjectsCount;

        // Time object used to trigger the clean up
        // procedure after a given period of idle time.
        Timer _idleTimer;

        #endregion

        #region Contructor
        /// <summary>
        /// Creates an instance of the
        /// ObjectPoolInstanceProvider class.
        /// </summary>
        /// <param name="instanceType">
        /// Type of the objects created in the pool.
        /// </param>
        /// <param name="maxPoolSize">
        /// Maximum number of objects can be created
        /// in the pool
        /// </param>
        /// <param name="minPoolSize">
        /// Minimum number of objects to be created, when
        /// initializing the pool.
        /// </param>
        /// <param name="creationTimeout">
        /// Object creation timeout value.
        /// </param>
        public ObjectPoolInstanceProvider(Type instanceType, int maxPoolSize,
            int minPoolSize, int creationTimeout)
        {
            _maxPoolSize = maxPoolSize;
            _minPoolSize = minPoolSize;
            _creationTimeout = creationTimeout;
            _instanceType = instanceType;

            // Varify the constructor args and
            // apply the default values if applicable.
            VerifyAttributes();

            _pool = new Stack<object>();
            _availableCount = new Semaphore(this._maxPoolSize, _maxPoolSize);

            // Initialize the timer and subscribe to
            // the "Elapsed" event.
            _idleTimer = new Timer(IDLE_TIMEOUT);
            _idleTimer.Elapsed += new System.Timers.ElapsedEventHandler(idleTimer_Elapsed);

            // Initialize the minmum number of objects
            // rquested
            Initialize();
        }

 
        #endregion

        #region IInstanceProvider Members
        /// <summary>
        /// Returns an object from the pool.
        /// </summary>
        /// <remark>
        /// This method is invoked by WCF runtime
        /// </remark>
        public object GetInstance(System.ServiceModel.InstanceContext instanceContext, System.ServiceModel.Channels.Message message)
        {
            return GetObjectFromThePool();
        }

        public object GetInstance(InstanceContext instanceContext)
        {
            return GetInstance(instanceContext, null);
        }

        /// <summary>
        /// Puts an object back to the pool.
        /// </summary>
        /// <remarks>
        /// This method is invoked by WCF runtime.
        /// </remarks>
        public void ReleaseInstance(InstanceContext instanceContext, object instance)
        {
            lock (_poolLock)
            {
                // Check whether the object can be pooled. 
                // Call the Deactivate method if possible.
                if (instance is IObjectPoolControl)
                {
                    IObjectPoolControl objectControl = (IObjectPoolControl)instance;
                    objectControl.Deactivate();

                    if (objectControl.CanBePooled)
                    {
                        _pool.Push(instance);

#if(DEBUG)
                        WritePoolMessage("MsgObjectPooled");
#endif
                    }
                    else
                    {
#if(DEBUG)
                        WritePoolMessage("MsgObjectWasNotPooled");
#endif
                    }
                }
                else
                {
                    _pool.Push(instance);

#if(DEBUG)
                    WritePoolMessage("MsgObjectPooled");
#endif
                }

                _activeObjectsCount--;

                if (_activeObjectsCount == 0)
                {
                    _idleTimer.Start();
                }
            }

            _availableCount.Release(1);
        }

        #endregion

        #region Helper Methods

        /// <summary>
        /// Initialize th minimum number of instances.
        /// </summary>
        private void Initialize()
        {
            for (int i = 0; i < _minPoolSize; i++)
            {
                _pool.Push(CreateNewPoolObject());
            }
        }

        /// <summary>
        /// Handles the instantiation of the types created by this 
        /// ObjectPool instance.
        /// </summary>        
        private object CreateNewPoolObject()
        {
            return Activator.CreateInstance(_instanceType);
        }

        /// <summary>
        /// Implements the logic for returning an object
        /// from the pool.
        /// </summary>        
        private object GetObjectFromThePool()
        {
            bool didNotTimeout =
                _availableCount.WaitOne(_creationTimeout, true);

            if (didNotTimeout)
            {
                object obj = null;

                lock (_poolLock)
                {
                    if (_pool.Count != 0)
                    {
                        obj = _pool.Pop();
                        _activeObjectsCount++;
                    }
                    else if (_pool.Count == 0)
                    {
                        if (_activeObjectsCount < _maxPoolSize)
                        {
                            obj = CreateNewPoolObject();
                            _activeObjectsCount++;

#if (DEBUG)
                            WritePoolMessage("MsgNewObject");
#endif
                        }
                    }

                    _idleTimer.Stop();
                }

                // Call the Activate method if possible.
                if (obj is IObjectPoolControl)
                {
                    ((IObjectPoolControl)obj).Activate();
                }

                return obj;
            }

            throw new TimeoutException("ExObjectCreationTimeout");
        }

        /// <summary>
        /// Clean up procedure.
        /// </summary>        
        void idleTimer_Elapsed(object sender, ElapsedEventArgs args)
        {
            _idleTimer.Stop();

            lock (_poolLock)
            {
                if (_activeObjectsCount == 0)
                {
                    // Remove the surplus objects.
                    if (_pool.Count > _minPoolSize)
                    {
                        while (_pool.Count != _minPoolSize)
                        {
#if(DEBUG)

                            WritePoolMessage("MsgObjectRemoving");

#endif

                            object removedItem = _pool.Pop();

                            if (removedItem is IDisposable)
                            {
                                ((IDisposable)removedItem).Dispose();
                            }
                        }
                    }
                    else if (_pool.Count < _minPoolSize)
                    {
                        // Reinitialize the missing objects.
                        while (_pool.Count != _minPoolSize)
                        {
                            _pool.Push(CreateNewPoolObject());
                        }
                    }
                }
            }
        }

        /// <summary>
        /// Writes a given message to the console
        /// in red color.
        /// </summary>        
        void WritePoolMessage(string message)
        {
            Console.ForegroundColor = ConsoleColor.Red;
            Console.WriteLine(message);
            Console.ForegroundColor = ConsoleColor.Gray;
        }

        /// <summary>
        /// Verifies the attribute values and 
        /// applies the defaults if applicable.
        /// </summary>
        void VerifyAttributes()
        {
            if (this._maxPoolSize == 0)
            {
                this._maxPoolSize = DEFAULT_MAX_POOL_SIZE;
            }

            if (this._minPoolSize == 0)
            {
                this._minPoolSize = DEFAULT_MIN_POOL_SIZE;
            }

            if (this._creationTimeout == 0)
            {
                this._creationTimeout = DEFAULT_CREATION_TIMEOUT;
            }
        }

        #endregion
    }
}
